/*
Copyright (c) 2008, Oleg S. Estekhin
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of the copyright holders nor the names of its
      contributors may be used to endorse or promote products derived from this
      software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package ebml.viewer;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.TransferHandler;
import javax.swing.filechooser.FileNameExtensionFilter;

import ebml.ElementDescriptorProvider;
import ebml.ElementDescriptorProviders;
import ebml.viewer.table.ExtendedTableModel;
import ebml.viewer.table.TableUtilities;
import ebml.viewer.tree.ExtendedTreeNodeCellRenderer;

/** The main EBML viewer frame. */
public final class ApplicationFrame extends JFrame {

    private static final ResourceBundle RESOURCES = ResourceBundle.getBundle(Resources.class.getName());

    private static final Preferences PREFERENCES = Preferences.userNodeForPackage(ApplicationFrame.class);


    private final ExecutorService executorService;

    private final ElementDescriptorProvider descriptorProvider;

    private final FileNameExtensionFilter ebmlExtensionFilter;

    private final JTabbedPane tabbedPane;


    private final ElementListTableModelProvider elementListTableModelProvider;

    private final FileListTableModelProvider fileListTableModelProvider;

    private final TypeStatsTableModelProvider typeStatsTableModelProvider;

    private final ElementStatsTableModelProvider elementStatsTableModelProvider;

    private final InspectionsTreeModelProvider inspectionsTreeModelProvider;


    /** Creates a new <code>ApplicationFrame</code> object. */
    public ApplicationFrame() {
        super(RESOURCES.getString("f.application.name"));

        executorService = Executors.newFixedThreadPool(1);
        descriptorProvider = ElementDescriptorProviders.loadAll();
        ebmlExtensionFilter = new FileNameExtensionFilter(RESOURCES.getString("filter.matroska.name"), "mkv", "mka", "mks");

        elementListTableModelProvider = new ElementListTableModelProvider(descriptorProvider);
        fileListTableModelProvider = new FileListTableModelProvider();
        typeStatsTableModelProvider = new TypeStatsTableModelProvider();
        elementStatsTableModelProvider = new ElementStatsTableModelProvider();
        inspectionsTreeModelProvider = new InspectionsTreeModelProvider();

        ActionMap actionMap = rootPane.getActionMap();
        actionMap.put("exit", new ExitAction());
        actionMap.put("open", new OpenAction());
        actionMap.put("parse", new ParseAction());
        actionMap.put("elementList", new ElementListAction());
        actionMap.put("fileList", new FileListAction());
        actionMap.put("typeStats", new TypeStatsAction());
        actionMap.put("elementStats", new ElementStatsAction());
        actionMap.put("inspections", new InspectionsAction());

        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                rootPane.getActionMap().get("exit").actionPerformed(null);
            }
        });

        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu fileMenu = menuBar.add(new JMenu(RESOURCES.getString("m.file.name")));
        fileMenu.setMnemonic('F');
        fileMenu.add(new JMenuItem(actionMap.get("open"))).setToolTipText(null);
        fileMenu.add(new JMenuItem(actionMap.get("parse"))).setToolTipText(null);
        fileMenu.addSeparator();
        fileMenu.add(new JMenuItem(actionMap.get("exit"))).setToolTipText(null);

        JMenu editMenu = menuBar.add(new JMenu(RESOURCES.getString("m.edit.name")));
        editMenu.setMnemonic('E');
        editMenu.add(new JMenuItem(actionMap.get("elementList"))).setToolTipText(null);

        JMenu viewMenu = menuBar.add(new JMenu(RESOURCES.getString("m.view.name")));
        viewMenu.setMnemonic('V');
        viewMenu.add(new JMenuItem(actionMap.get("fileList"))).setToolTipText(null);

        JMenu analyseMenu = menuBar.add(new JMenu(RESOURCES.getString("m.analyse.name")));
        analyseMenu.setMnemonic('A');
        analyseMenu.add(new JMenuItem(actionMap.get("typeStats"))).setToolTipText(null);
        analyseMenu.add(new JMenuItem(actionMap.get("elementStats"))).setToolTipText(null);
        analyseMenu.addSeparator();
        analyseMenu.add(new JMenuItem(actionMap.get("inspections"))).setToolTipText(null);

        tabbedPane = new JTabbedPane();
        getContentPane().add(tabbedPane, BorderLayout.CENTER);

        setTransferHandler(new FileTransferHandler());

        int preferredWidth = PREFERENCES.getInt("width", 0);
        int preferredHeight = PREFERENCES.getInt("height", 0);
        if (preferredWidth > 0 && preferredHeight > 0) {
            rootPane.setPreferredSize(new Dimension(preferredWidth, preferredHeight));
        }
        pack();
        if (PREFERENCES.getBoolean("maximized", false)) {
            setExtendedState(MAXIMIZED_BOTH);
        }
        rootPane.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                if ((getExtendedState() & MAXIMIZED_BOTH) == MAXIMIZED_BOTH) {
                    PREFERENCES.putBoolean("maximized", true);
                } else {
                    PREFERENCES.putBoolean("maximized", false);
                    PREFERENCES.putInt("width", rootPane.getWidth());
                    PREFERENCES.putInt("height", rootPane.getHeight());
                }
            }
        });
    }


    @Override
    public void dispose() {
        executorService.shutdownNow();
        super.dispose();
    }


    /**
     * Parses the file and opens a new tab with the file contents.
     *
     * @param file the file
     */
    public void openFile(final File file) {
        FileStructureTreeModelProvider listener = new FileStructureTreeModelProvider(elementListTableModelProvider);
        JTree tree = new JTree(listener.getTreeModel());
        tree.setCellRenderer(new ExtendedTreeNodeCellRenderer());
        int index = tabbedPane.getTabCount();
        tabbedPane.insertTab(file.getName(), null, new JScrollPane(tree), null, index);
        tabbedPane.setTabComponentAt(index, new ClosableTabComponent(tabbedPane));
        tabbedPane.setSelectedIndex(index);
        FileTask task = new FileTask(file, descriptorProvider);
        task.addFractalModelListener(listener);
        addFileTask(task);
    }

    /**
     * Parses the file.
     *
     * @param file the file
     */
    public void parseFile(File file) {
        addFileTask(new FileTask(file, descriptorProvider));
    }


    private void addFileTask(FileTask task) {
        fileListTableModelProvider.openFile(task.getFile());
        task.addFractalModelListener(fileListTableModelProvider);
        task.addFractalModelListener(typeStatsTableModelProvider);
        task.addFractalModelListener(elementStatsTableModelProvider);
        task.addFractalModelListener(inspectionsTreeModelProvider);
        executorService.execute(task);
    }

    public void editElementList(boolean select) {
        String title = RESOURCES.getString("tab.elementList.name");
        int index = tabbedPane.indexOfTab(title);
        if (index < 0) {
            index = tabbedPane.getTabCount();
            ExtendedTableModel model = elementListTableModelProvider.getTableModel();
            JTable table = new JTable(model, TableUtilities.createTableColumnModel(model, PREFERENCES.node("elementListTableModel")));
            table.setRowSorter(TableUtilities.createRowSorter(model));
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            table.setFillsViewportHeight(true);
            tabbedPane.insertTab(title, null, new JScrollPane(table), null, index);
            tabbedPane.setTabComponentAt(index, new ClosableTabComponent(tabbedPane));
        }
        if (select) {
            tabbedPane.setSelectedIndex(index);
        }
    }

    public void viewFileList(boolean select) {
        String title = RESOURCES.getString("tab.fileList.name");
        int index = tabbedPane.indexOfTab(title);
        if (index < 0) {
            index = tabbedPane.getTabCount();
            ExtendedTableModel model = fileListTableModelProvider.getTableModel();
            JTable table = new JTable(model, TableUtilities.createTableColumnModel(model, PREFERENCES.node("fileListTableModel")));
            table.setRowSorter(TableUtilities.createRowSorter(model));
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            table.setFillsViewportHeight(true);
            tabbedPane.insertTab(title, null, new JScrollPane(table), null, index);
            tabbedPane.setTabComponentAt(index, new ClosableTabComponent(tabbedPane));
        }
        if (select) {
            tabbedPane.setSelectedIndex(index);
        }
    }

    public void viewTypeStats(boolean select) {
        String title = RESOURCES.getString("tab.typeStats.name");
        int index = tabbedPane.indexOfTab(title);
        if (index < 0) {
            index = tabbedPane.getTabCount();
            ExtendedTableModel model = typeStatsTableModelProvider.getTableModel();
            JTable table = new JTable(model, TableUtilities.createTableColumnModel(model, PREFERENCES.node("typeStatsTableModel")));
            table.setRowSorter(TableUtilities.createRowSorter(model));
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            table.setFillsViewportHeight(true);
            tabbedPane.insertTab(title, null, new JScrollPane(table), null, index);
            tabbedPane.setTabComponentAt(index, new ClosableTabComponent(tabbedPane));
        }
        if (select) {
            tabbedPane.setSelectedIndex(index);
        }
    }

    public void viewElementStats(boolean select) {
        String title = RESOURCES.getString("tab.elementStats.name");
        int index = tabbedPane.indexOfTab(title);
        if (index < 0) {
            index = tabbedPane.getTabCount();
            ExtendedTableModel model = elementStatsTableModelProvider.getTableModel();
            JTable table = new JTable(model, TableUtilities.createTableColumnModel(model, PREFERENCES.node("elementStatsTableModel")));
            table.setRowSorter(TableUtilities.createRowSorter(model));
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            table.setFillsViewportHeight(true);
            tabbedPane.insertTab(title, null, new JScrollPane(table), null, index);
            tabbedPane.setTabComponentAt(index, new ClosableTabComponent(tabbedPane));
        }
        if (select) {
            tabbedPane.setSelectedIndex(index);
        }
    }

    public void viewTypeInspections(boolean select) {
        String title = RESOURCES.getString("tab.inspections.name");
        int index = tabbedPane.indexOfTab(title);
        if (index < 0) {
            index = tabbedPane.getTabCount();
            JTree tree = new JTree(inspectionsTreeModelProvider.getTreeModel());
            tree.setRootVisible(false);
            tree.setShowsRootHandles(true);
            tree.setCellRenderer(new ExtendedTreeNodeCellRenderer());
            tabbedPane.insertTab(title, null, new JScrollPane(tree), null, index);
            tabbedPane.setTabComponentAt(index, new ClosableTabComponent(tabbedPane));
        }
        if (select) {
            tabbedPane.setSelectedIndex(index);
        }
    }


    private final class FileTransferHandler extends TransferHandler {

        @Override
        public boolean canImport(TransferSupport support) {
            return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
        }

        @Override
        public boolean importData(TransferSupport support) {
            if (!canImport(support)) {
                return false;
            }
            Transferable transferable = support.getTransferable();
            try {
                @SuppressWarnings("unchecked")
                List<File> files = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
                boolean imported = false;
                for (File file : files) {
                    imported = importFile(file, support.getDropAction()) || imported;
                }
            } catch (UnsupportedFlavorException ignored) {
                return false;
            } catch (IOException ignored) {
                return false;
            }
            return true;
        }

        private boolean importFile(File file, int dropAction) {
            boolean result = false;
            if (file.isDirectory()) {
                for (File f : file.listFiles()) {
                    result = importFile(f, dropAction) || result;
                }
            } else if (ebmlExtensionFilter.accept(file)) {
                result = true;
                if ((dropAction & DnDConstants.ACTION_COPY) == DnDConstants.ACTION_COPY) {
                    openFile(file);
                } else {
                    parseFile(file);
                    viewFileList(false);
                }
            }
            return result;
        }

    }


    private final class ExitAction extends AbstractAction {

        private ExitAction() {
            super(RESOURCES.getString("a.exit.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.exit.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_X);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            dispose();
        }

    }

    private abstract class AbstractOpenAction extends AbstractAction {

        private JFileChooser fileChooser;

        protected AbstractOpenAction(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (fileChooser == null) {
                fileChooser = new JFileChooser(PREFERENCES.get("currentDirectoryPath", null));
                fileChooser.setMultiSelectionEnabled(true);
                fileChooser.setFileView(new NativeFileView());
                fileChooser.addChoosableFileFilter(ebmlExtensionFilter);
            }
            if (fileChooser.showOpenDialog(ApplicationFrame.this) == JFileChooser.APPROVE_OPTION) {
                PREFERENCES.put("currentDirectoryPath", fileChooser.getCurrentDirectory().getAbsolutePath());
                for (File file : fileChooser.getSelectedFiles()) {
                    processFile(file);
                }
            }
        }

        protected abstract void processFile(File file);

    }

    private final class OpenAction extends AbstractOpenAction {

        private OpenAction() {
            super(RESOURCES.getString("a.open.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.open.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_O);
            putValue(SMALL_ICON, new ImageIcon(OpenAction.class.getResource("open.png")));
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
        }

        @Override
        protected void processFile(File file) {
            openFile(file);
        }

    }

    private final class ParseAction extends AbstractOpenAction {

        private ParseAction() {
            super(RESOURCES.getString("a.parse.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.parse.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_P);
        }

        @Override
        protected void processFile(File file) {
            parseFile(file);
            viewFileList(false);
        }

    }


    private final class ElementListAction extends AbstractAction {

        private ElementListAction() {
            super(RESOURCES.getString("a.elementList.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.elementList.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_E);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            editElementList(true);
        }

    }

    private final class FileListAction extends AbstractAction {

        private FileListAction() {
            super(RESOURCES.getString("a.fileList.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.fileList.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_F);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            viewFileList(true);
        }

    }

    private final class TypeStatsAction extends AbstractAction {

        private TypeStatsAction() {
            super(RESOURCES.getString("a.typeStats.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.typeStats.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_T);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            viewTypeStats(true);
        }

    }

    private final class ElementStatsAction extends AbstractAction {

        private ElementStatsAction() {
            super(RESOURCES.getString("a.elementStats.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.elementStats.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_E);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            viewElementStats(true);
        }

    }

    private final class InspectionsAction extends AbstractAction {

        private InspectionsAction() {
            super(RESOURCES.getString("a.inspections.name"));
            putValue(SHORT_DESCRIPTION, RESOURCES.getString("a.inspections.desc"));
            putValue(MNEMONIC_KEY, KeyEvent.VK_I);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            viewTypeInspections(true);
        }

    }

}
